namespace FIREFLYX {
    /**
     * 灯光收集器
     */
    export class LightCollector {
        /** 能收集 的附加最大灯光数量 */
        public static readonly MAX_ADD_LIGHT_COUNT = 64;
        private static readonly HELP_VEC_3 = new Vector3();

        //相机ubo 对象
        private _ubo: Nullable<WebGLBuffer>;
        //ubo block 尺寸
        private _uboBlockSize: number;
        //主光源 方向 数据
        private _mainLightDirF32: Float32Array;
        //主光源  颜色 数据
        private _mainLightColorF32: Float32Array;
        //附加光源  位置 数据
        private _addLightsPosF32: Float32Array;
        //附加光源  颜色 数据
        private _addLightsColorF32: Float32Array;
        //附加光源  衰减信息 数据
        private _addLightsAttF32: Float32Array;
        //附加光源  射灯方向 数据
        private _addLightsSpotDirF32: Float32Array;

        //主光源
        private _mainLight: Nullable<Light>;
        //附加的光源
        private _addLights: Light[];
        //收集到的光源
        private _collectedLights: Light[];
        //需要更新数据
        private _needUpdateUbo: boolean = false;
        //灯ID map
        private _lightIndexMap: { [id: number]: number } = {};

        /** 光源数据的 ubo对象 */
        public get ubo() { return this._ubo; }
        /** ubo块byte占用大小 */
        public get uboBlockSize() { return this._uboBlockSize; }
        /** 主光源 */
        public get mainLight() { return this._mainLight; }

        constructor() {
            this._addLights = [];
            this._collectedLights = [];
            const addN = 4 * LightCollector.MAX_ADD_LIGHT_COUNT;
            //array init
            this._mainLightDirF32 = new Float32Array(4);
            this._mainLightColorF32 = new Float32Array(4);
            this._addLightsPosF32 = new Float32Array(addN);
            this._addLightsColorF32 = new Float32Array(addN);
            this._addLightsAttF32 = new Float32Array(addN);
            this._addLightsSpotDirF32 = new Float32Array(addN);
            //init ubo
            const gl = App.webgl2;
            this._ubo = gl.createBuffer();
            //bind start
            gl.bindBuffer(gl.UNIFORM_BUFFER, this._ubo);
            //初始化 buffer 设置大小 4 = float
            const lihgtBlockSize = this._uboBlockSize = 4 * (4 + 4 + addN + addN + addN + addN);
            gl.bufferData(gl.UNIFORM_BUFFER, lihgtBlockSize, gl.DYNAMIC_DRAW);
            //bind end
            gl.bindBuffer(gl.UNIFORM_BUFFER, null);
        }

        /**
         * 启用灯光
         * @param light 
         */
        public enableLight(light: Light) {
            if (!light || !light.entity || this._mainLight == light || this._collectedLights.indexOf(light) != -1) return;
            //是否是 mainLight
            if (light.type == LightType.DIRECTIONAL && this._mainLight == null) {
                this._mainLight = light;
                this._needUpdateUbo = true;
            } else { //是否能 加到附加light
                this._collectedLights.push(light);
                if (this._addLights.length < LightCollector.MAX_ADD_LIGHT_COUNT) {
                    this._lightIndexMap[light.getInstanceID()] = this._addLights.length;    //记录索引到map
                    this._addLights.push(light);
                    this._needUpdateUbo = true;
                }
            }

            //监听enable的灯光
            light.addListener(`type_set`, this.onLightTypeSet, this);
            light.addListener(`color_set`, this.onLightColorIntensitySet, this);
            light.addListener(`intensity_set`, this.onLightColorIntensitySet, this);
            light.addListener(`range_set`, this.onLightRangeSet, this);
            light.addListener(`spotAngle_set`, this.onLightSpotAngleSet, this);
            light.entity.transform.addListener(`postion_set`, this.onLightPostionSet, this);
            light.entity.transform.addListener(`rotation_set`, this.onLightRotationSet, this);
        }

        /**
         * 关闭灯光
         * @param light 
         */
        public disableLight(light: Light) {
            //
            if (!light || (!this._mainLight && this._collectedLights.length == 0)) return;
            let cLights = this._collectedLights;
            let lights = this._addLights;
            let needUpdate = true;
            //是否是 mainLight
            if (light == this._mainLight) {
                this._mainLight = null;
                //add 中寻找下一个 main Light
                for (let i = 0, len = cLights.length; i < len; i++) {
                    const l = cLights[i];
                    if (l.type == LightType.DIRECTIONAL) {
                        this._mainLight = l;
                        break;
                    }
                }
            } else { //是否是 附加light
                let idx = cLights.indexOf(light);
                cLights.splice(idx, 1);
                idx = lights.indexOf(light);
                if (idx != -1) {
                    cLights.splice(idx, 1);

                    //将 _collectedLights 填入 _addLights
                    if (cLights.length >= LightCollector.MAX_ADD_LIGHT_COUNT) {
                        lights.length = cLights.length;
                        this._lightIndexMap = {};
                        cLights.forEach((v) => {
                            this._lightIndexMap[v.getInstanceID()] = lights.length;
                            lights.push(v);
                        });
                    }
                } else {
                    needUpdate = false; //只是 清理了 _collectedLights 中的数据
                }
            }

            this._needUpdateUbo = needUpdate;

            //取消监听disable的灯光
            light.removeListener(`type_set`, this.onLightTypeSet, this);
            light.removeListener(`color_set`, this.onLightColorIntensitySet, this);
            light.removeListener(`intensity_set`, this.onLightColorIntensitySet, this);
            light.removeListener(`range_set`, this.onLightRangeSet, this);
            light.removeListener(`spotAngle_set`, this.onLightSpotAngleSet, this);
            if (light.entity) {
                light.entity.transform.removeListener(`postion_set`, this.onLightPostionSet, this);
                light.entity.transform.removeListener(`rotation_set`, this.onLightRotationSet, this);
            }
        }

        /**
         * 刷新 ubo 的数据
         * @returns 
         */
        public refreshUboData() {
            if (!this._needUpdateUbo) return;
            this._needUpdateUbo = false;
            //mainLight
            const _ml = this._mainLight;
            const _mlDir = LightCollector.HELP_VEC_3;
            let _mlCR = 0, _mlCG = 0, _mlCB = 0;
            _mlDir.set(0, 0, 0);
            if (_ml) {
                //direction
                if (_ml.entity) {
                    _ml.entity.transform.getForwardToRef(_mlDir);
                }
                //color
                const mlI = _ml.intensity;
                _mlCR = _ml.color.r * mlI;
                _mlCG = _ml.color.g * mlI;
                _mlCB = _ml.color.b * mlI;
            }
            this._mainLightDirF32[0] = _mlDir.x;
            this._mainLightDirF32[1] = _mlDir.y;
            this._mainLightDirF32[2] = _mlDir.z;
            this._mainLightDirF32[3] = 0;

            this._mainLightColorF32[0] = _mlCR;
            this._mainLightColorF32[1] = _mlCG;
            this._mainLightColorF32[2] = _mlCB;
            this._mainLightColorF32[3] = 1;

            //additional lihgts
            for (let i = 0, len = this._addLights.length; i < len; i++) {
                //
                const sIdx = 4 * i;
                const _addl = this._addLights[i];
                let _cR = 0, _cG = 0, _cB = 0;
                let _posX = 0, _posY = 0, _posZ = 0;
                if (_addl) {
                    //direction
                    if (_addl.entity) {
                        const addlPos = _addl.entity.transform.position;
                        _posX = addlPos.x;
                        _posY = addlPos.y;
                        _posZ = addlPos.z;
                    }
                    //color
                    const addlI = _addl.intensity;
                    _cR = _addl.color.r * addlI;
                    _cG = _addl.color.g * addlI;
                    _cB = _addl.color.b * addlI;

                    //attenuation

                    //spotDir
                    switch (_addl.type) {
                        case LightType.POINT:

                            break;
                        case LightType.SPOT:

                            break;
                        default:
                    }
                }

                this._addLightsPosF32[sIdx + 0] = _posX;
                this._addLightsPosF32[sIdx + 1] = _posY;
                this._addLightsPosF32[sIdx + 2] = _posZ;
                this._addLightsPosF32[sIdx + 3] = 0;

                this._addLightsColorF32[sIdx + 0] = _cR;
                this._addLightsColorF32[sIdx + 1] = _cG;
                this._addLightsColorF32[sIdx + 2] = _cB;
                this._addLightsColorF32[sIdx + 3] = 1;
            }

            const addByteN = 4 * LightCollector.MAX_ADD_LIGHT_COUNT * 4;

            const gl = App.webgl2;
            //ubo bind start
            gl.bindBuffer(gl.UNIFORM_BUFFER, this._ubo);

            //set data
            let bOffset = 0;
            gl.bufferSubData(gl.UNIFORM_BUFFER, bOffset, this._mainLightDirF32);
            bOffset += 4 * 4;
            gl.bufferSubData(gl.UNIFORM_BUFFER, bOffset, this._mainLightColorF32);
            bOffset += 4 * 4;
            gl.bufferSubData(gl.UNIFORM_BUFFER, bOffset, this._addLightsPosF32);
            bOffset += addByteN;
            gl.bufferSubData(gl.UNIFORM_BUFFER, bOffset, this._addLightsColorF32);
            bOffset += addByteN;
            gl.bufferSubData(gl.UNIFORM_BUFFER, bOffset, this._addLightsAttF32);
            bOffset += addByteN;
            gl.bufferSubData(gl.UNIFORM_BUFFER, bOffset, this._addLightsSpotDirF32);

            gl.bindBuffer(gl.UNIFORM_BUFFER, null);
        }

        /**
         * 设置 灯光 颜色 和 强度 到 UBO
         * @param light 灯光
         * @returns 
         */
        private setColorIntensity2Ubo(light: Light) {
            if (!light) return;
            //ubo
            const gl = App.webgl2;
            gl.bindBuffer(gl.UNIFORM_BUFFER, this._ubo);

            if (light == this._mainLight) {
                const _color = this._mainLight.color;
                const _intensity = this._mainLight.intensity;
                this._mainLightColorF32[0] = _color.r * _intensity;
                this._mainLightColorF32[1] = _color.g * _intensity;
                this._mainLightColorF32[2] = _color.b * _intensity;
                this._mainLightColorF32[3] = 1;
                //set ubo buffer
                gl.bufferSubData(gl.UNIFORM_BUFFER, 4 * 4, this._mainLightColorF32);

            } else {
                const lIndex = this._lightIndexMap[light.getInstanceID()];
                if (lIndex == null) {
                    const _addl = this._addLights[lIndex];
                    const _color = _addl.color;
                    const _intensity = _addl.intensity;
                    const sIdx = 4 * lIndex;
                    this._addLightsColorF32[sIdx + 0] = _color.r * _intensity;
                    this._addLightsColorF32[sIdx + 1] = _color.g * _intensity;
                    this._addLightsColorF32[sIdx + 2] = _color.b * _intensity;
                    this._addLightsColorF32[sIdx + 3] = 1;

                    const addByteN = 4 * LightCollector.MAX_ADD_LIGHT_COUNT * 4;
                    //set ubo buffer
                    gl.bufferSubData(gl.UNIFORM_BUFFER, addByteN, this._addLightsColorF32);

                }
            }

            gl.bindBuffer(gl.UNIFORM_BUFFER, null);
        }

        /**
         * 设置 灯光 方向 到 UBO
         * @param light 灯光
         * @returns 
         */
        private setDirection2Ubo(light: Light) {
            const e = light.entity;
            if (!light || !e || light.type == LightType.POINT) return;
            //ubo
            const gl = App.webgl2;
            if (light == this._mainLight) {
                const _mlDir = LightCollector.HELP_VEC_3;
                e.transform.getForwardToRef(_mlDir);
                this._mainLightDirF32[0] = _mlDir.x;
                this._mainLightDirF32[1] = _mlDir.y;
                this._mainLightDirF32[2] = _mlDir.z;
                this._mainLightDirF32[3] = 0;
                gl.bindBuffer(gl.UNIFORM_BUFFER, this._ubo);
                //set to ubo buffer
                gl.bufferSubData(gl.UNIFORM_BUFFER, 0, this._mainLightDirF32);
            } else {
                switch (light.type) {
                    case LightType.DIRECTIONAL:
                        break;
                    case LightType.SPOT:
                        break;
                    default:
                }
            }

            //ubo bind null
            gl.bindBuffer(gl.UNIFORM_BUFFER, null);
        }

        private onLightTypeSet(light: Light) {
            if (!light) return;
            if (light == this._mainLight) {
                if (light.type != LightType.DIRECTIONAL) {
                    this._needUpdateUbo = true;
                }
            } else {
                switch (light.type) {
                    case LightType.DIRECTIONAL:
                        break;
                    case LightType.POINT:
                        break;
                    case LightType.SPOT:
                        break;
                    default:
                }
            }
        }

        /** 当 灯光 颜色 和 强度 有变化 */
        private onLightColorIntensitySet(light: Light) {
            this.setColorIntensity2Ubo(light);
        }

        private onLightRangeSet(light: Light) {
            if (!light || light.type == LightType.DIRECTIONAL) return;
        }

        private onLightSpotAngleSet(light: Light) {
            if (!light || light.type != LightType.SPOT) return;
        }

        private onLightPostionSet(transform: Transform) {
            const e = transform.entity;
            if (!e) return;
            const light = e.light;
            if (!light || light.type == LightType.DIRECTIONAL) return;
        }

        private onLightRotationSet(transform: Transform) {
            const e = transform.entity;
            if (!e || !e.light) return;
            this.setDirection2Ubo(e.light);
        }
    }
}